/*
 * Copyright 2008 Robert Hanson <iamroberthanson AT gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.penlets.util;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;

import com.livescribe.configuration.Config;
import com.livescribe.penlet.PenletContext;
import com.penlets.io.IOUtils;

/**
 * An implementation of com.livescribe.configuration.Config that
 * uses the internal penlet storage for storing the values.
 * @author robert.hanson
 */
public class FileBasedConfig extends Config
{
    private static Hashtable configMap = new Hashtable(0);

    private Hashtable valueMap = new Hashtable();
    private PenletContext context;
    private String path;
    private IOUtils io;
    private Config defaultConfig;
    

    private FileBasedConfig (PenletContext context, String path)
    {
        this.context = context;
        this.path = path;
        this.io = new IOUtils(context);
    }
    

    /**
     * Creates a new Config at the specified path in the penlet internal storage.
     * If an instance of this Config already exists for the specified
     * path, the same instance will be returned without creating a separate
     * instance.
     * @param context a PenletContext
     * @param path the path to the configuration file storage
     * @return a Config instance
     */
    public static Config getConfig (PenletContext context, String path) throws IOException
    {
        if (configMap.containsKey(path)) {
            return (Config) configMap.get(path);
        }
        FileBasedConfig result = new FileBasedConfig(context, path);
        result.loadFromFileIfExists(path);
        configMap.put(path, result);
        return result;
    }

    /**
     * Creates a new Config at the specified path in the penlet internal storage.
     * If an instance of this Config already exists for the specified
     * path, the same instance will be returned without creating a separate
     * instance.  Default values are loaded from a file in the internal penlet
     * storage.
     * @param context a PenletContext
     * @param path the path to the configuration file storage
     * @param defaultPath a file containing of default values
     * @return a Config instance
     */
    public static Config getConfigWithFileDefault (PenletContext context, String path, String defaultPath) throws IOException
    {
        FileBasedConfig result = (FileBasedConfig) getConfig(context, path);
        result.setDefaultAsFileConfig(defaultPath);
        return result;
    }

    
    /**
     * Creates a new Config at the specified path in the penlet internal storage.
     * If an instance of this Config already exists for the specified
     * path, the same instance will be returned without creating a separate
     * instance.  Default values are loaded from a file in the JAR.
     * @param context a PenletContext
     * @param path the path to the configuration file storage
     * @param defaultPath a file containing of default values
     * @return a Config instance
     */
    public static Config getConfigWithResFileDefault (PenletContext context, String path, String defaultPath) throws IOException
    {
        FileBasedConfig result = (FileBasedConfig) getConfig(context, path);
        result.setDefaultAsResFileConfig(defaultPath);
        return result;
    }


    /**
     * Creates a new Config at the specified path in the penlet internal storage.
     * If an instance of this Config already exists for the specified
     * path, the same instance will be returned without creating a separate
     * instance.  Default values are loaded from the penlet's config file.
     * @param context a PenletContext
     * @param path the path to the configuration file storage
     * @return a Config instance
     */
    public static Config getConfigWithAppDefault (PenletContext context, String path) throws IOException
    {
        FileBasedConfig result = (FileBasedConfig) getConfig(context, path);
        result.setDefaultAsAppConfig();
        return result;
    }

    
    private void setDefaultAsFileConfig (String defaultPath) throws IOException
    {
        defaultConfig = FileBasedConfig.getConfig(context, defaultPath);
    }

    private void setDefaultAsResFileConfig (String defaultPath) throws IOException
    {
        if (configMap.containsKey(path)) {
            defaultConfig = (Config) configMap.get(path);
        }
        FileBasedConfig result = new FileBasedConfig(context, path);
        result.loadFromResFileIfExists(path);
        configMap.put(path, result);
        defaultConfig = result;
    }
    
    private void setDefaultAsAppConfig ()
    {
        defaultConfig = context.getAppConfiguration();
    }


    private void loadFromFileIfExists (String path) throws IOException
    {
        Properties props = Properties.loadPropertiesFromString(io.readFile(path));
        for (Enumeration names = props.getEnumeratedNames(); names.hasMoreElements();) {
            String name = (String) names.nextElement();
            valueMap.put(name, props.getProperty(name).trim());
        }
    }

    private void loadFromResFileIfExists (String path) throws IOException
    {
        Properties props = Properties.loadProperties(path);
        for (Enumeration names = props.getEnumeratedNames(); names.hasMoreElements();) {
            String name = (String) names.nextElement();
            valueMap.put(name, props.getProperty(name).trim());
        }
    }

    /**
     * Flushes changes to the file.  This method must be called prior
     * to the penlet completion, or modifications to config values
     * will not be persisted.
     * @throws IOException
     */
    public void flush () throws IOException
    {
        StringBuffer outBuf = new StringBuffer();
        for (Enumeration keys = valueMap.keys(); keys.hasMoreElements();) {
            String key = (String) keys.nextElement();
            outBuf.append(key).append("=").append(valueMap.get(key)).append("\n");
        }
        io.writeFile(path, outBuf.toString(), true);
    }
    
    
    /**
     * Returns a boolean value for the specified name. Values stored
     * as "1", "yes", "true", or "t" will be returned as true. All
     * other values are returned as false.
     */
    public boolean getBooleanValue (String name)
    {
        if (!valueMap.containsKey(name) && defaultConfig != null) {
            return defaultConfig.getBooleanValue(name);
        }
        
        String strVal = (String) valueMap.get(name);
        if ("1".equalsIgnoreCase(strVal) || "yes".equalsIgnoreCase(strVal) || "true".equalsIgnoreCase(strVal)
                || "t".equalsIgnoreCase(strVal)) {
            return true;
        }
        return false;
    }

    /**
     * Returns a double value for the specified name. Returns 0
     * if the value is not a valid double value.
     */
    public double getDoubleValue (String name)
    {
        if (!valueMap.containsKey(name) && defaultConfig != null) {
            return defaultConfig.getDoubleValue(name);
        }
        
        double result = 0;
        try {
            result = Double.parseDouble((String) valueMap.get(name));
        }
        catch (Exception e) {
        }
        return result;
    }

    /**
     * Returns a long value for the specified name. Returns 0
     * if the value is not a valid long value.
     */
    public long getLongValue (String name)
    {
        if (!valueMap.containsKey(name) && defaultConfig != null) {
            return defaultConfig.getLongValue(name);
        }
        
        long result = 0;
        try {
            result = Long.parseLong((String) valueMap.get(name));
        }
        catch (Exception e) {
        }
        return result;
    }

    /**
     * Returns a String value for the specified name.
     */
    public String getStringValue (String name)
    {
        if (!valueMap.containsKey(name) && defaultConfig != null) {
            return defaultConfig.getStringValue(name);
        }
        return (String) valueMap.get(name);
    }


    /**
     * Stores a boolean value.
     */
    public void setBooleanValue (String name, boolean value)
    {
        valueMap.put(name, new Boolean(value).toString());
    }

    /**
     * Stores a double value.
     */
    public void setDoubleValue (String name, double value)
    {
        valueMap.put(name, new Double(value).toString());
    }

    /**
     * Stores a long value.
     */
    public void setLongValue (String name, long value)
    {
        valueMap.put(name, new Long(value).toString());
    }

    /**
     * Stores a String value.
     */
    public void setStringValue (String name, String value)
    {
        valueMap.put(name, value);
    }
}
